package hydrap2p.modulemanager;

import hydrap2p.ServiceLocation;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.helper.ThriftServerThread;
import hydrap2p.helper.ThriftUtils;
import hydrap2p.helper.Utility;
import hydrap2p.modulemanager.listen.Listeners;
import hydrap2p.modulemanager.listen.ThriftListener;
import hydrap2p.proxy.manager.ProxyManager;
import hydrap2p.servicemanager.NameTaken;
import hydrap2p.servicemanager.ServiceListener;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import org.apache.thrift.TException;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;

public class ModuleManagerServer implements ModuleManager.Iface {

	private final ModuleController controller;
	private ModuleManagerServiceListener serviceListener;
	private ServerSocket socket;
	private TServerTransport servertransport;
	private TServer server;
	private ThriftServerThread listenerthread;
	private ProxyManager.Iface proxy;
	
	/**
	 * Throws an exception if could not start listening, start module, or register with it
	 * @param controller
	 * @throws IOException
	 */
	public ModuleManagerServer(ModuleController controller) throws IOException
	{
		this.controller=controller;
		proxy=null;
		
		initServerSocket();
		
		initListener();
		
		registerLocation();
		
		startListener();
		
		controller.setServer(this);
	}

	public void start()
	{
		servertransport = new TServerSocket(socket);
		TThreadPoolServer.Args args = new TThreadPoolServer.Args(servertransport);
		args.processor(new ModuleManager.Processor(this));
		server = new TThreadPoolServer(args);
		server.serve();
	}
	
	private void initServerSocket() throws IOException
	{
		Random ports=new Random();
		int minport=1025;
		int maxport=50000;
		
		System.out.println("Starting ModuleManager");
		socket = new ServerSocket();
		int port=ports.nextInt(maxport-minport)+minport;
		int attempts=30;
		while (socket.getLocalPort()==-1 && attempts>0)
		{
			System.out.println("Trying to bind to port "+port);
			try {
				socket.bind(new InetSocketAddress("localhost",port));
			}
			catch (Exception e) {}
			if (socket.getLocalPort()==-1)
				port=ports.nextInt(maxport-minport)+minport;
			attempts--;
		}
		if (socket.getLocalPort()==-1)
			throw(new BindException("Could not find a port to bind"));
		System.err.println("Bound ModuleManager to port "+socket.getLocalPort());
		
	}
	
	private void initListener()
	{
		serviceListener=new ModuleManagerServiceListener(controller);
	}
	
	private void registerLocation() throws BindException
	{
		ServiceLocation here=new ServiceLocation();
		here.address="localhost";
		here.port=socket.getLocalPort();
		try {
			if (ServiceConnector.getServiceManager().supportedInterfaces().contains("ProxyManager"))
			{
				proxy=ServiceConnector.getService(ProxyManager.Client.class, new ServiceLocation("localhost",Utility.getManagerPort()));
				serviceListener.registerHook("ModuleManager", new ProxyRegisterListener(controller, proxy,here), true);
			}
			else
			{
				ServiceConnector.getServiceManager().setServiceLocation("ModuleManager", here);
			}
		} catch (TException e) {
			e.printStackTrace();
			throw(new BindException("Failed to tell ServiceManager about my location"));
		} catch (NameTaken e) {
			throw(new BindException("Some other component has already claimed my interface location: "+e.getLocation().address+":"+e.getLocation().port));
		} catch (NullPointerException e) {
			throw(new BindException("Failed to connect to ServiceManager"));
		} catch (NumberFormatException e) {
			// Could not parse Manager port location
		} catch (FileNotFoundException e) {
			// Could not parse Manager port location
		}
		System.out.println("Registered ModuleManager at ServiceManager");
	}
	
	private void startListener() throws IOException {
		try {
			listenerthread=ThriftUtils.initThriftServer(new ServiceListener.Processor(serviceListener), null);
			listenerthread.start();
		} catch (BindException e) {
			throw e;
		} catch (IOException e) {
			throw e;
		} catch (NameTaken e) {
			throw new IOException("Couldn't register at ServiceManager");
		}
		try {
			boolean ret=ServiceConnector.getServiceManager().subscribe(listenerthread.getLocation());
			if (!ret)
				throw new IOException("Couldn't subscribe to ServiceManager, nicely");
		} catch (TException e) {
			e.printStackTrace();
			throw new IOException("Couldn't subscribe to ServiceManager");
		}
	}
	
	@Override
	public void ping() throws TException {
		
	}

	@Override
	public List<String> supportedInterfaces() throws TException {
		return Arrays.asList("Service", "ModuleManager");
	}

	@Override
	public List<ModuleInfo> getModuleList() throws TException {
		return controller.getAllModules();
	}

	@Override
	public void setModuleAutostart(String arg0, String arg1, boolean arg2) throws TException {
		controller.setModuleAutostart(arg0, arg1, arg2);
	}

	@Override
	public void shutdown() {
		controller.shutdown();
		server.stop();
		servertransport.close();
	}

	@Override
	public void startModule(String arg0, String arg1) {
		controller.start(arg0, arg1);
	}

	@Override
	public void stopModule(String arg0, String arg1) {
		controller.stop(arg0, arg1);
	}

	@Override
	public boolean subscribe(ServiceLocation arg0) {
		ThriftListener listener = new ThriftListener(arg0);
		try {
			listener.connect();
		} catch (Exception e) {
			// Invalid listener
			return false;
		}
		Listeners.addListener(listener);
		return true;
	}

	@Override
	public void unsubscribe(ServiceLocation arg0) throws TException {
		ThriftListener listener = new ThriftListener(arg0);
		Listeners.delListener(listener);
		
	}

	public void stoplistener() {
		if (server!=null)
			server.stop();
		if (listenerthread!=null)
			listenerthread.shutdown();
		try {
			if (servertransport!=null)
				servertransport.close();
		} catch (Exception e) {
			// Should be closed now
		}
		try {
			if (socket!=null)
				socket.close();
		} catch (IOException e) {
			// Should be closed now
		}
	}

}
